class Snowflake {
  private readonly epoch: bigint = 1609459200000n; // 2021-01-01 00:00:00 UTC
  private readonly workerIdBits: bigint = 5n;
  private readonly datacenterIdBits: bigint = 5n;
  private readonly sequenceBits: bigint = 12n;

  private readonly maxWorkerId: bigint = -1n ^ (-1n << this.workerIdBits);
  private readonly maxDatacenterId: bigint =
    -1n ^ (-1n << this.datacenterIdBits);

  private readonly workerIdShift: bigint = this.sequenceBits;
  private readonly datacenterIdShift: bigint =
    this.sequenceBits + this.workerIdBits;
  private readonly timestampShift: bigint =
    this.datacenterIdShift + this.datacenterIdBits;

  private sequence: bigint = 0n;
  private lastTimestamp: bigint = -1n;

  constructor(
    private readonly workerId: bigint,
    private readonly datacenterId: bigint
  ) {
    if (workerId > this.maxWorkerId || workerId < 0n) {
      throw new Error(`Worker ID must be between 0 and ${this.maxWorkerId}`);
    }
    if (datacenterId > this.maxDatacenterId || datacenterId < 0n) {
      throw new Error(
        `Datacenter ID must be between 0 and ${this.maxDatacenterId}`
      );
    }
  }

  public nextId(): bigint {
    let timestamp = BigInt(Date.now());

    if (timestamp < this.lastTimestamp) {
      throw new Error("Clock moved backwards. Refusing to generate id");
    }

    if (this.lastTimestamp === timestamp) {
      this.sequence = (this.sequence + 1n) & ((-1n << this.sequenceBits) ^ -1n);
      if (this.sequence === 0n) {
        timestamp = this.waitNextMillis(this.lastTimestamp);
      }
    } else {
      this.sequence = 0n;
    }

    this.lastTimestamp = timestamp;

    return (
      ((timestamp - this.epoch) << this.timestampShift) |
      (this.datacenterId << this.datacenterIdShift) |
      (this.workerId << this.workerIdShift) |
      this.sequence
    );
  }

  private waitNextMillis(lastTimestamp: bigint): bigint {
    let timestamp = BigInt(Date.now());
    while (timestamp <= lastTimestamp) {
      timestamp = BigInt(Date.now());
    }
    return timestamp;
  }
}

// 使用示例
const snowflake = new Snowflake(1n, 1n);
// console.log(snowflake.nextId().toString());
export function getNextCode() {
  return snowflake.nextId().toString();
}
